//
// Created by Administrator on 2016/9/9.
//

#include <mutex>
#include <cstring>
#include "File.h"

#include <stdio.h>  /* defines FILENAME_MAX */

#ifdef _WIN32
#define getCurrentDir _getcwd
#else
#include <unistd.h>
#define getCurrentDir getcwd
#endif

using namespace ZainTL;
using namespace std;

FileStat File::getFileStat() const {
    return mFileStat;
}

std::string File::getAbsolutePath() const {
    return *mAbsolutePath;
}

std::string File::getRelativePath() const {
    return *mRelativePath;
}

std::string File::getParentPath() const {
    return *mParentPath;
}

std::string File::getName() const {
    return *mName;
}

File::~File() {
    if (mAbsolutePath) {
        delete mAbsolutePath;
        mAbsolutePath = nullptr;
    }
    if (mRelativePath) {
        delete mRelativePath;
        mRelativePath = nullptr;
    }
    if (mName) {
        delete (mName);
        mName = nullptr;
    }
    if (mParentPath) {
        delete (mParentPath);
        mParentPath = nullptr;
    }
    if (mInputStream) {
        delete (mInputStream);
        mInputStream = nullptr;
    }
    if (mOutputStream) {
        delete (mOutputStream);
        mOutputStream = nullptr;
    }
}

File::File(const char *path)
        : mAbsolutePath(new string(path)), mRelativePath(new string("")), mParentPath(new string("")) {
    replace(mAbsolutePath->begin(), mAbsolutePath->end(), '\\', '/'); // replace all '\\' to '/'
#if defined(__linux__)
    if (mAbsolutePath->find('/') != 0) {
        char cCurrentPath[FILENAME_MAX];
        getCurrentDir(cCurrentPath, sizeof(cCurrentPath));
        string currentPath(cCurrentPath);
        if (mAbsolutePath->find("..") == 0) {
            currentPath = currentPath.substr(0, currentPath.find_last_of('/'));
            string *oldMAbsolutePath = mAbsolutePath;
            mAbsolutePath = new string(oldMAbsolutePath->substr(2));
            delete (oldMAbsolutePath);
        } else{
            mAbsolutePath->insert(0,"/");
        }
        mAbsolutePath->insert(0, currentPath);
    }
#elif defined(_WIN32)

    if (mAbsolutePath->find(':') != 1) {
        char cCurrentPath[FILENAME_MAX];
        getCurrentDir(cCurrentPath, sizeof(cCurrentPath));
        string currentPath(cCurrentPath);
        if (mAbsolutePath->find("..") == 0) {
            currentPath = currentPath.substr(0, currentPath.find_last_of('\\'));
            string *oldMAbsolutePath = mAbsolutePath;
            mAbsolutePath = new string(oldMAbsolutePath->substr(2));
            delete (oldMAbsolutePath);
        }else if(mAbsolutePath->find("/") != 0){
            mAbsolutePath->insert(0,"/");
        }
        mAbsolutePath->insert(0, currentPath);
    }
#endif

    const char *path1=mAbsolutePath->c_str();
    DIR *dir = NULL;
    struct stat fileStat;
    dir = opendir(path1);
    /*stat() success will return 0*/
    if (stat(path1, &fileStat) == 0) {
        if (S_ISDIR(fileStat.st_mode)) {
            if (dir != NULL) {
                mFileStat = Is_Dir;
            } else {
                mFileStat = Is_Dir_Invalid;
            }
        } else {
            mFileStat = Is_File;
        }
    }
    if (dir) {
        closedir(dir);
    }
}

char *File::getFileData(uint64_t &length) {
    ifstream file(*mAbsolutePath, fstream::in | fstream::binary);
    if (!file) {
        return NULL;
    }

    /*new array by file's length*/
    file.seekg(0, fstream::end);
    uint64_t size = (uint64_t) file.tellg();
    char *outCharArr = new char[size];
    file.seekg(0, fstream::beg);

    length = 0;
    while (length < size) {
        outCharArr[length++] = file.get();
    }
    file.close();

    return outCharArr;
}

FileInputStream *File::getFileInputStream() {
    //非同步块中检查(可以保证不用每次都加锁解锁)
    if (mInputStream == nullptr) {
        mutex mtx;
        mtx.lock();
        //同步块中检查
        if (mInputStream == nullptr) {
            mInputStream = new FileInputStream(mAbsolutePath->c_str());
        }
        mtx.unlock();
    }
    return mInputStream;
}

FileOutputStream *File::getFileOutputStream() {
    //非同步块中检查
    if (mOutputStream == nullptr) {
        mutex mtx;
        mtx.lock();
        //同步块中检查
        if (mOutputStream == nullptr) {
            mOutputStream = new FileOutputStream(mAbsolutePath->c_str());
        }
        mtx.unlock();
    }
    return mOutputStream;
}

void File::listChildren(function<void(ZainTL::File *)> onChildFile) {
    DIR *dir = NULL;
    struct dirent *ent = NULL;
    dir = opendir(mAbsolutePath->c_str());
    if (dir) {
        //list directory
        while ((ent = readdir(dir)) != NULL) {
            if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
                continue;
            }
            string subPath = *mAbsolutePath + "/" + ent->d_name;
            File *child = new File(subPath.c_str());
            delete child->mRelativePath;
            child->mRelativePath = new string(*mRelativePath);
            child->mRelativePath->append("/").append(ent->d_name);
            child->mParentFile = this;
            onChildFile(child);
            delete (child);
        }
        closedir(dir);
    }
}
